import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Task } from './entities/task.entity';
import { TaskTemplate } from './entities/task-template.entity';
import {
  CreateTaskDto,
  UpdateTaskDto,
  QueryTaskDto,
  PaginatedTaskResponseDto,
  TaskResponseDto,
} from './dto';
import { TaskStatus, TaskType, PaginationMetaDto } from '../common';
import { UsersService } from '../users/users.service';

@Injectable()
export class TasksService {
  constructor(
    @InjectRepository(Task)
    private readonly taskRepository: Repository<Task>,
    @InjectRepository(TaskTemplate)
    private readonly taskTemplateRepository: Repository<TaskTemplate>,
    private readonly usersService: UsersService,
  ) {}

  /**
   * Map a Task entity to a TaskResponseDto
   * @param task Task entity
   * @returns TaskResponseDto
   */
  mapTaskToDto(task: Task): TaskResponseDto {
    if (!task) return null;

    const dto = new TaskResponseDto();
    dto.id = task.id;
    dto.title = task.title;
    dto.description = task.description;
    dto.type = task.type;
    dto.taskCategoryId = task.taskCategoryId;
    dto.parentTaskId = task.parentTaskId;
    dto.assignedUserId = task.assignedUserId;
    dto.startTime = task.startTime;
    dto.status = task.status;
    dto.dueTime = task.dueTime;
    dto.completedAt = task.completedAt;
    dto.cronExpression = task.cronExpression;
    dto.createdAt = task.createdAt;
    dto.updatedAt = task.updatedAt;
    dto.taskTemplateId = task.taskTemplateId;

    // Add related entity data if available
    if (task.taskCategory) {
      dto.taskCategoryName = task.taskCategory.name;
    }

    if (task.assignedUser) {
      dto.assignedUserName = task.assignedUser.displayName || task.assignedUser.username;
    }

    return dto;
  }

  /**
   * Map an array of Task entities to TaskResponseDto array
   * @param tasks Array of Task entities
   * @returns Array of TaskResponseDto
   */
  mapTasksToDtos(tasks: Task[]): TaskResponseDto[] {
    if (!tasks) return [];
    return tasks.map((task) => this.mapTaskToDto(task));
  }

  /**
   * Map a paginated Task response to a paginated TaskResponseDto
   * @param paginatedTasks Paginated Task response
   * @returns Paginated TaskResponseDto
   */
  mapPaginatedResponse(paginatedTasks: {
    data: Task[];
    pagination: any;
  }): PaginatedTaskResponseDto {
    const result = new PaginatedTaskResponseDto();
    result.data = this.mapTasksToDtos(paginatedTasks.data);
    result.pagination = paginatedTasks.pagination;
    return result;
  }

  /**
   * Create a new task
   * @param createTaskDto Task creation data
   * @returns Created task
   */
  async create(createTaskDto: CreateTaskDto): Promise<Task> {
    // Check if task is created from a template
    if (createTaskDto.taskTemplateId) {
      const template = await this.taskTemplateRepository.findOne({
        where: { id: createTaskDto.taskTemplateId, isDeleted: false },
      });

      if (!template) {
        throw new BadRequestException(
          `Task template with ID ${createTaskDto.taskTemplateId} not found or deleted`,
        );
      }
    }

    // Validate user exists if assignedUserId is provided
    if (createTaskDto.assignedUserId) {
      const user = await this.usersService.findOne(createTaskDto.assignedUserId);
      if (!user) {
        throw new BadRequestException(`User with ID ${createTaskDto.assignedUserId} not found`);
      }
    }

    // Validate parent task if provided
    if (createTaskDto.parentTaskId) {
      const parentTask = await this.findOne(createTaskDto.parentTaskId);
      if (!parentTask) {
        throw new BadRequestException(
          `Parent task with ID ${createTaskDto.parentTaskId} not found`,
        );
      }
    }

    // Create and save the task
    const task = this.taskRepository.create({
      ...createTaskDto,
      startTime: createTaskDto.startTime ? new Date(createTaskDto.startTime) : null,
      dueTime: createTaskDto.dueTime ? new Date(createTaskDto.dueTime) : null,
    });

    return this.taskRepository.save(task);
  }

  /**
   * Find all tasks with optional filtering
   * @param filters Optional filters for tasks
   * @returns Array of tasks
   */
  async findAll(filters?: {
    assignedUserId?: string;
    status?: TaskStatus;
    title?: string;
    parentTaskId?: string;
    taskTemplateId?: string;
    type?: string;
    includeDeleted?: boolean;
  }): Promise<Task[]> {
    const queryBuilder = this.taskRepository.createQueryBuilder('task');

    // Default condition: exclude deleted tasks unless explicitly requested
    if (!filters?.includeDeleted) {
      queryBuilder.where('task.isDeleted = :isDeleted', { isDeleted: false });
    }

    // Apply filters if provided
    if (filters) {
      if (filters.assignedUserId) {
        queryBuilder.andWhere('task.assignedUserId = :userId', {
          userId: filters.assignedUserId,
        });
      }

      if (filters.status) {
        queryBuilder.andWhere('task.status = :status', { status: filters.status });
      }

      if (filters.title) {
        queryBuilder.andWhere('task.title LIKE :title', { title: `%${filters.title}%` });
      }

      if (filters.parentTaskId) {
        queryBuilder.andWhere('task.parentTaskId = :parentId', {
          parentId: filters.parentTaskId,
        });
      }

      if (filters.taskTemplateId) {
        queryBuilder.andWhere('task.taskTemplateId = :templateId', {
          templateId: filters.taskTemplateId,
        });
      }

      if (filters.type) {
        // Handle comma-separated types
        const types = filters.type.split(',').map((t) => t.trim());
        if (types.length > 0) {
          queryBuilder.andWhere('task.type IN (:...types)', { types });
        }
      }
    }

    // Add default sorting by createdAt DESC
    queryBuilder.orderBy('task.createdAt', 'DESC');

    return queryBuilder.getMany();
  }

  /**
   * Find all tasks with pagination and optional filtering
   * @param queryParams Query parameters including pagination, sorting, and filters
   * @returns Paginated task response
   */
  async findAllPaginated(queryParams: {
    page?: number;
    pageSize?: number;
    status?: TaskStatus;
    title?: string;
    parentTaskId?: string;
    taskTemplateId?: string;
    type?: string;
    assignedUserId?: string;
    includeDeleted?: boolean;
    sortBy?: string;
    sortDirection?: 'ASC' | 'DESC';
  }): Promise<PaginatedTaskResponseDto> {
    const {
      page = 1,
      pageSize = 10,
      status,
      title,
      parentTaskId,
      taskTemplateId,
      type,
      assignedUserId,
      includeDeleted = false,
      sortBy = 'createdAt',
      sortDirection = 'DESC',
    } = queryParams;

    // Ensure page and pageSize are numbers
    const pageNum = Number(page);
    const pageSizeNum = Number(pageSize);

    // Calculate skip for pagination
    const skip = (pageNum - 1) * pageSizeNum;

    // Create query builder
    const queryBuilder = this.taskRepository.createQueryBuilder('task');

    // Default condition: exclude deleted tasks unless explicitly requested
    if (!includeDeleted) {
      queryBuilder.where('task.isDeleted = :isDeleted', { isDeleted: false });
    }

    // Apply filters if provided
    if (assignedUserId) {
      queryBuilder.andWhere('task.assignedUserId = :userId', {
        userId: assignedUserId,
      });
    }

    if (status) {
      queryBuilder.andWhere('task.status = :status', { status });
    }

    if (title) {
      queryBuilder.andWhere('task.title LIKE :title', { title: `%${title}%` });
    }

    if (parentTaskId) {
      queryBuilder.andWhere('task.parentTaskId = :parentId', {
        parentId: parentTaskId,
      });
    }

    if (taskTemplateId) {
      queryBuilder.andWhere('task.taskTemplateId = :templateId', {
        templateId: taskTemplateId,
      });
    }

    if (type) {
      // Handle comma-separated types
      const types = type.split(',').map((t) => t.trim());
      if (types.length > 0) {
        queryBuilder.andWhere('task.type IN (:...types)', { types });
      }
    }

    // Add sorting
    queryBuilder.orderBy(`task.${sortBy}`, sortDirection);

    // Get total count for pagination
    const total = await queryBuilder.getCount();

    // Get paginated results
    const tasks = await queryBuilder
      .skip(skip)
      .take(pageSizeNum)
      .leftJoinAndSelect('task.assignedUser', 'assignedUser')
      .leftJoinAndSelect('task.taskCategory', 'taskCategory')
      .leftJoinAndSelect('task.parentTask', 'parentTask')
      .getMany();

    // Create pagination metadata
    const pagination: PaginationMetaDto = {
      total,
      page: pageNum,
      pageSize: pageSizeNum,
    };

    // Create and return the paginated response
    const response = new PaginatedTaskResponseDto();
    response.data = tasks;
    response.pagination = pagination;

    return response;
  }

  /**
   * Find a task by ID
   * @param id Task ID
   * @returns Task or throws NotFoundException
   */
  async findOne(id: string): Promise<Task> {
    const task = await this.taskRepository.findOne({
      where: { id, isDeleted: false },
      relations: ['assignedUser', 'parentTask'],
    });

    if (!task) {
      throw new NotFoundException(`Task with ID ${id} not found`);
    }

    return task;
  }

  /**
   * Update a task
   * @param id Task ID
   * @param updateTaskDto Task update data
   * @returns Updated task
   */
  async update(id: string, updateTaskDto: UpdateTaskDto): Promise<Task> {
    const task = await this.findOne(id);

    // Validate user if changing
    if (updateTaskDto.assignedUserId && updateTaskDto.assignedUserId !== task.assignedUserId) {
      const user = await this.usersService.findOne(updateTaskDto.assignedUserId);
      if (!user) {
        throw new BadRequestException(`User with ID ${updateTaskDto.assignedUserId} not found`);
      }
    }

    // Validate parent task if changing
    if (updateTaskDto.parentTaskId && updateTaskDto.parentTaskId !== task.parentTaskId) {
      const parentTask = await this.findOne(updateTaskDto.parentTaskId);
      if (!parentTask) {
        throw new BadRequestException(
          `Parent task with ID ${updateTaskDto.parentTaskId} not found`,
        );
      }

      // Prevent circular references
      if (parentTask.id === task.id) {
        throw new BadRequestException(`Task cannot be its own parent`);
      }
    }

    // Process date fields
    if (updateTaskDto.startTime) {
      updateTaskDto.startTime = new Date(updateTaskDto.startTime) as any;
    }

    if (updateTaskDto.dueTime) {
      updateTaskDto.dueTime = new Date(updateTaskDto.dueTime) as any;
    }

    // Update and save
    Object.assign(task, updateTaskDto);
    return this.taskRepository.save(task);
  }

  /**
   * Mark a task as completed
   * @param id Task ID
   * @returns Updated task
   */
  async markAsCompleted(id: string): Promise<Task> {
    const task = await this.findOne(id);

    task.status = TaskStatus.COMPLETED;
    task.completedAt = new Date();

    // If completedAt is provided, we could store it in a separate field or metadata
    // For now, we just update the task status

    return this.taskRepository.save(task);
  }

  /**
   * Mark a task as not started (revert from completed)
   * @param id Task ID
   * @returns Updated task
   */
  async markAsNotStarted(id: string): Promise<Task> {
    const task = await this.findOne(id);

    task.status = TaskStatus.NOT_START;
    task.completedAt = null; // Clear the completion timestamp

    return this.taskRepository.save(task);
  }

  /**
   * Soft delete a task
   * @param id Task ID
   */
  async remove(id: string): Promise<void> {
    const task = await this.findOne(id);

    task.isDeleted = true;
    await this.taskRepository.save(task);
  }

  /**
   * Mark a task as removed (for users)
   * @param id Task ID
   * @returns Updated task
   */
  async markAsRemoved(id: string): Promise<Task> {
    const task = await this.findOne(id);

    task.status = TaskStatus.REMOVED;

    return this.taskRepository.save(task);
  }

  /**
   * Create a task from a template
   * @param templateId Template ID
   * @param createFromTemplateDto Task creation data
   * @returns Created task
   */
  async createFromTemplate(templateId: string, createFromTemplateDto: any): Promise<Task> {
    const template = await this.taskTemplateRepository.findOne({
      where: { id: templateId, isDeleted: false },
    });

    if (!template) {
      throw new NotFoundException(`Task template with ID ${templateId} not found or deleted`);
    }

    // Validate user if provided
    if (createFromTemplateDto.assignedUserId) {
      const user = await this.usersService.findOne(createFromTemplateDto.assignedUserId);
      if (!user) {
        throw new BadRequestException(
          `User with ID ${createFromTemplateDto.assignedUserId} not found`,
        );
      }
    }

    // Create task from template
    const task = this.taskRepository.create({
      title: template.title,
      description: template.description,
      type: createFromTemplateDto.assignedUserId ? TaskType.SINGLE : TaskType.GLOBAL,
      assignedUserId: createFromTemplateDto.assignedUserId,
      startTime: createFromTemplateDto.startTime ? new Date(createFromTemplateDto.startTime) : null,
      dueTime: createFromTemplateDto.dueTime ? new Date(createFromTemplateDto.dueTime) : null,
      taskTemplateId: templateId,
      taskCategoryId: template.taskCategoryId,
    });

    return this.taskRepository.save(task);
  }

  /**
   * Find tasks for the current user (assigned tasks and global tasks)
   * @param userId Current user ID
   * @param queryDto Query parameters including pagination and sorting
   * @returns Paginated tasks
   */
  async findTasksForUser(
    userId: string,
    queryDto: QueryTaskDto,
  ): Promise<PaginatedTaskResponseDto> {
    const {
      page = 1,
      pageSize = 10,
      status,
      title,
      parentTaskId,
      taskTemplateId,
      type,
      sortBy = 'createdAt',
      sortDirection = 'DESC',
    } = queryDto;

    // Ensure page and pageSize are numbers
    const pageNum = Number(page);
    const pageSizeNum = Number(pageSize);

    // Calculate skip for pagination
    const skip = (pageNum - 1) * pageSizeNum;

    // Create query builder
    const queryBuilder = this.taskRepository.createQueryBuilder('task');

    // Always exclude deleted tasks for user queries
    queryBuilder.where('task.isDeleted = :isDeleted', { isDeleted: false });

    // Get tasks that are either global or assigned to the current user
    queryBuilder.andWhere('(task.type = :globalType OR task.assignedUserId = :userId)', {
      globalType: TaskType.GLOBAL,
      userId,
    });

    // Apply filters if provided
    if (status) {
      queryBuilder.andWhere('task.status = :status', { status });
    }

    if (title) {
      queryBuilder.andWhere('task.title LIKE :title', { title: `%${title}%` });
    }

    if (parentTaskId) {
      queryBuilder.andWhere('task.parentTaskId = :parentId', {
        parentId: parentTaskId,
      });
    }

    if (taskTemplateId) {
      queryBuilder.andWhere('task.taskTemplateId = :templateId', {
        templateId: taskTemplateId,
      });
    }

    if (type) {
      // Handle comma-separated types
      const types = type.split(',').map((t) => t.trim());
      if (types.length > 0) {
        queryBuilder.andWhere('task.type IN (:...types)', { types });
      }
    }

    // Add sorting
    queryBuilder.orderBy(`task.${sortBy}`, sortDirection);

    // Get total count for pagination
    const total = await queryBuilder.getCount();

    // Get paginated results
    const tasks = await queryBuilder
      .skip(skip)
      .take(pageSizeNum)
      .leftJoinAndSelect('task.assignedUser', 'assignedUser')
      .leftJoinAndSelect('task.taskCategory', 'taskCategory')
      .leftJoinAndSelect('task.parentTask', 'parentTask')
      .getMany();

    // Create pagination metadata
    const pagination: PaginationMetaDto = {
      total,
      page: pageNum,
      pageSize: pageSizeNum,
    };

    // Create and return the paginated response
    const response = new PaginatedTaskResponseDto();
    response.data = tasks;
    response.pagination = pagination;

    return response;
  }
}
